/**
Copyright (c) 2007-2013 Alysson Bessani, Eduardo Alchieri, Paulo Sousa, and the authors indicated in the @author tags

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package bftsmart.demo.ycsb;

import bftsmart.consensus.app.BatchAppResultImpl;
import bftsmart.tom.MessageContext;
import bftsmart.tom.ReplyContextMessage;
import bftsmart.tom.ServiceReplica;
import bftsmart.tom.server.defaultservices.DefaultRecoverable;

import java.io.*;
import java.util.List;
import java.util.TreeMap;

/**
 * 
 * @author Marcel Santos
 *
 */
public class YCSBServer extends DefaultRecoverable {
	private static boolean _debug	= false;
	private TreeMap<String, YCSBTable>	mTables;
	
	private boolean logPrinted = false;
	
	public static void main(String[] args) throws Exception	{
		if (args.length == 1) {
			new YCSBServer(new Integer(args[0]));
		} else {
			printUsage();
		}
	}

	private static void printUsage() {
		System.out.println("Usage: java YCSBServer <replica_id>");
	}

	private YCSBServer(int id) {
		this.mTables = new TreeMap<String, YCSBTable>();
    	new ServiceReplica(id, this, this);
	}

	@Override
	public byte[][] appExecuteBatch(byte[][] commands, MessageContext[] msgCtx, boolean fromConsensus) {
		byte[][] replies = new byte[commands.length][];
		int index = 0;
    	for(byte[] command: commands) {
    		if(msgCtx != null && msgCtx[index] != null && msgCtx[index].getConsensusId() % 1000 == 0 && !logPrinted) {
    			System.out.println("YCSBServer executing CID: " + msgCtx[index].getConsensusId());
    			logPrinted = true;
    		} else
    			logPrinted = false;
    			
			YCSBMessage aRequest = YCSBMessage.getObject(command);
			YCSBMessage reply = YCSBMessage.newErrorMessage("");
			if (aRequest == null) {
				replies[index] = reply.getBytes();
				continue;
			}
			if (_debug)
				System.out.println("[INFO] Processing an ordered request");
			switch (aRequest.getType()) {
				case CREATE: { // ##### operation: create #####
					switch (aRequest.getEntity()) {
						case RECORD: // ##### entity: record #####
							if (!mTables.containsKey(aRequest.getTable())) {
								mTables.put((String) aRequest.getTable(), new YCSBTable());
							}
							if (!mTables.get(aRequest.getTable()).containsKey(
									aRequest.getKey())) {
								mTables.get(aRequest.getTable()).put(aRequest.getKey(), aRequest.getValues());
								reply = YCSBMessage.newInsertResponse(0);
							}
							break;
						default: // Only create records
							break;
					}
					break;
				}
				
				case UPDATE: { // ##### operation: update #####
					switch (aRequest.getEntity()) {
						case RECORD: // ##### entity: record #####
							if (!mTables.containsKey(aRequest.getTable())) {
								mTables.put((String) aRequest.getTable(), new YCSBTable());
							}
							mTables.get(aRequest.getTable()).put(aRequest.getKey(), aRequest.getValues());
							reply = YCSBMessage.newUpdateResponse(1);
							break;
						default: // Only update records
							break;
					}
					break;
				}
			}
			if (_debug)
				System.out.println("[INFO] Sending reply");
	        replies[index++] = reply.getBytes();
		}
//		System.out.println("RETURNING REPLY");
		return replies;
	}

	@Override
	public byte[][] appExecuteBatch(byte[][] commands, MessageContext[] msgCtxs, boolean fromConsensus, List<ReplyContextMessage> replyContextMessages) {
		return appExecuteBatch(commands, msgCtxs, fromConsensus);
	}

	@Override
	public byte[] appExecuteUnordered(byte[] theCommand, MessageContext theContext) {
		YCSBMessage aRequest = YCSBMessage.getObject(theCommand);
		YCSBMessage reply = YCSBMessage.newErrorMessage("");
		if (aRequest == null) {
			return reply.getBytes();
		}
		if (_debug)
			System.out.println("[INFO] Processing an unordered request");

		switch (aRequest.getType()) {
			case READ: { // ##### operation: read #####
				switch (aRequest.getEntity()) {
					case RECORD: // ##### entity: record #####
						if(!mTables.containsKey(aRequest.getTable())) {
							reply = YCSBMessage.newErrorMessage("Table not found");
							break;
						}
						if(!mTables.get(aRequest.getTable()).containsKey(aRequest.getKey())) {
							reply = YCSBMessage.newErrorMessage("Record not found");
							break;
						} else {
							reply = YCSBMessage.newReadResponse(mTables.get(aRequest.getTable()).get(aRequest.getKey()), 0);
							break;
						}
				}
			}
		}
		if (_debug)
			System.out.println("[INFO] Sending reply");
		return reply.getBytes();
	}

	@Override
	public BatchAppResultImpl preComputeAppHash(byte[][] commands) {
		return null;
	}

	@Override
	public List<byte[]> updateAppResponses(List<byte[]> asyncResponseLinkedList) {
		return null;
	}

	@Override
	public void preComputeAppCommit(String batchId) {

	}

	@Override
	public void preComputeAppRollback(String batchId) {

	}

	@SuppressWarnings("unchecked")
	@Override
	public void installSnapshot(byte[] state) {
		try {
			System.out.println("setState called");
			ByteArrayInputStream bis = new ByteArrayInputStream(state);
			ObjectInput in = new ObjectInputStream(bis);
			mTables = (TreeMap<String, YCSBTable>) in.readObject();
			in.close();
			bis.close();
		} catch (Exception e) {
			System.err.println("[ERROR] Error deserializing state: "
					+ e.getMessage());
		}
	}

	@Override
	public byte[] getSnapshot() {
		try {
			System.out.println("getState called");
			ByteArrayOutputStream bos = new ByteArrayOutputStream();
			ObjectOutput out = new ObjectOutputStream(bos);
			out.writeObject(mTables);
			out.flush();
			bos.flush();
			out.close();
			bos.close();
			return bos.toByteArray();
		} catch (IOException ioe) {
			System.err.println("[ERROR] Error serializing state: "
					+ ioe.getMessage());
			return "ERROR".getBytes();
		}
	}

}
